﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using X3DXNA.interfaces;
using System.Reflection;
using X3DXNA.X3DSchema;

namespace X3DXNA.core.ROUTEHandler
{
    public class MyRoute
    {

        #region Private Fields

        List<ROUTE> routes;
        List<String> routedNodes;
        List<IRoutable> routedObjects;

        List<IRoutable> listOfSourceNodes = new List<IRoutable>();
        List<IRoutable> listOfDestinationsNodes = new List<IRoutable>();
        List<PropertyInfo> listOfSourceProperties = new List<PropertyInfo>();
        List<PropertyInfo> listOfDestinationProperties = new List<PropertyInfo>();
        List<MethodInfo> listOfSourceMethods = new List<MethodInfo>();
        List<MethodInfo> listOfDestinationMethods = new List<MethodInfo>();

        #endregion

        #region Constructor

        public MyRoute(X3D x3d)
        {
            routedObjects = new List<IRoutable>();
            routes = new List<ROUTE>();

            findROUTES(x3d);
            routedNodes = extractRoutedNotes(routes);
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Inizializza i routes. Da eseguire una volta che la scena X3D è stata analizzata
        /// </summary>
        public void initializeMyRoute()
        {
            computeRoutes();
        }

        /// <summary>
        /// Aggiorna i ROUTES, copiando i valori delle proprietà! Metodo grezzo, non orientato agli
        /// eventi.
        /// </summary>
        public void UpdateRoutes()
        {
            //for (int i = 0; i < listOfSourceNodes.Count; i++)
            //{
            //    object value = listOfSourceProperties[i].GetValue(listOfSourceNodes[i], null);
            //    if (value != null)
            //    {
            //        //Convert.ChangeType(value, listOfSourceProperties[i].PropertyType);
            //        listOfDestinationProperties[i].SetValue(listOfDestinationsNodes[i], value, null);
            //    }
            //}

            for (int i = 0; i < listOfSourceNodes.Count; i++)
            {
                object value = listOfSourceMethods[i].Invoke(listOfSourceNodes[i], null);
                if (value != null)
                {
                    //Convert.ChangeType(value, listOfSourceProperties[i].PropertyType);
                    listOfDestinationMethods[i].Invoke(listOfDestinationsNodes[i], new object[]{value});
                }
            }
        }

        /// <summary>
        /// Controlla se un nodo è contenuto nella lista dei ROUTED.
        /// </summary>
        /// <param name="node">Il nodo da controllare.</param>
        /// <returns>Il risultato della ricerca: true se contenuto, false altrimenti.</returns>
        public bool checkRoutedObject(INode node)
        {
            if (routedNodes != null && routedNodes.Contains(node.DEF))
                return true;
            else
                return false;
        }

        /// <summary>
        /// Aggiunge un oggetto alla lista dei Routed.
        /// </summary>
        /// <param name="routedObject">L'oggetto da aggiungere.</param>
        public void addRoutedObject(IRoutable routedObject)
        {
            this.routedObjects.Add(routedObject);
        }

        #endregion

        #region Private Methods

        private void computeRoutes()
        {
            int numberOfRoutes = routes.Count;

            for (int i = 0; i < numberOfRoutes; i++)
            {
                IRoutable fromNode = findRoutable(routes[i].fromNode);
                IRoutable toNode = findRoutable(routes[i].toNode);

                if (fromNode != null && toNode != null)
                {
                    //PropertyInfo sourceProperty = fromNode.GetType().GetProperty(routes[i].fromField);
                    //PropertyInfo destinationProperty = toNode.GetType().GetProperty(routes[i].toField);

                    //MethodInfo sourceMethod = fromNode.GetType().GetMethod("get_" + routes[i].fromField);
                    //MethodInfo destinationMethod = toNode.GetType().GetMethod("set_" + routes[i].toField);

                    //if (sourceProperty != null && 
                    //    destinationProperty != null && 
                    //    sourceProperty.GetType().Equals(destinationProperty.GetType()) &&
                    //    sourceMethod.ReturnParameter.GetType().Equals(destinationMethod.ReturnParameter.GetType()))
                    //{
                    //    listOfSourceProperties.Add(sourceProperty);
                    //    listOfDestinationProperties.Add(destinationProperty);
                    //    listOfSourceNodes.Add(fromNode);
                    //    listOfDestinationsNodes.Add(toNode);
                    //    listOfSourceMethods.Add(sourceMethod);
                    //    listOfDestinationMethods.Add(destinationMethod);
                    //}

                    MethodInfo sourceMethod = fromNode.GetType().GetMethod("get_" + routes[i].fromField);
                    String prefix;
                    if (routes[i].toField.Contains("set_"))
                        prefix = "";
                    else 
                        prefix = "set_";
                    MethodInfo destinationMethod = toNode.GetType().GetMethod(prefix + routes[i].toField);

                    if (sourceMethod!= null &&
                        destinationMethod != null &&
                        sourceMethod.ReturnParameter.GetType().Equals(destinationMethod.ReturnParameter.GetType()))
                    {
                        listOfSourceNodes.Add(fromNode);
                        listOfDestinationsNodes.Add(toNode);
                        listOfSourceMethods.Add(sourceMethod);
                        listOfDestinationMethods.Add(destinationMethod);
                    }
                }
            }
        }

        /// <summary>
        /// Dando per assunto che ogni oggetto IRoutable sia "conservato" nella lista "routedObjects", questa funzione
        /// restituisce l'oggetto cercato, tramite il suo DEF.
        /// </summary>
        /// <param name="DEF">Il DEF, che identifica l'oggetto.</param>
        /// <returns>Lo IRoutable cercato.</returns>
        private IRoutable findRoutable(String DEF)
        {
            return routedObjects.Find(delegate(IRoutable routed)
            {
                return (routed.DEF == DEF);
            });
        }

        /// <summary>
        /// Da una lista di ROUTE tira fuori i DEF dei nodi interessati, senza duplicati.
        /// </summary>
        /// <param name="routes">La lista da cui estrarre i DEF.</param>
        /// <returns>La lista di String computata.</returns>
        private List<String> extractRoutedNotes(List<ROUTE> routes)
        {
            List<String> routedNodes = new List<string>();

            foreach (ROUTE route in routes)
            {
                routedNodes.Add(route.fromNode);
                routedNodes.Add(route.toNode);
            }

            if (routedNodes.Count > 0)
            {
                routedNodes = routedNodes.Distinct().ToList();
                return routedNodes;
            }
            else
                return null;
        }

        /// <summary>
        /// Trova tutti i ROUTES presenti nella scena e li restituisce sotto forma di lista.
        /// </summary>
        /// <param name="node">Il nodo da cui partire la ricerca.</param>
        /// <returns>Lista di ROUTE, se esistono ROUTE nella scena, null altrimenti.</returns>
        private void findROUTES(INode node)
        {
            if (node != null)
            {
                if (node is ROUTE)
                    routes.Add((ROUTE)node);

                if (node.Childs != null)
                    foreach (INode child in node.Childs)
                        findROUTES(child);
            }
        }

        #endregion

    }
}
